/* Copyright (c) 2001 - 2013 OpenPlans - www.openplans.org. All rights reserved.
 * This code is licensed under the GPL 2.0 license, available at the root
 * application directory.
 */
package org.geoserver.wms.kvp;

import java.awt.Color;
import java.awt.image.IndexColorModel;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Loads a JASC Pal files into an {@link IndexColorModel}.
 * 
 * <p>
 * I made a real minor extension to the usual form of a JASC pal file which
 * allows us to provide values in the #ffffff or 0Xffffff hex form.
 * 
 * <p>
 * Note that this kind of file does not support explicitly setting transparent
 * pixel. However I have implemented this workaround, if you use less than 256
 * colors in your paletteInverter I will accordingly set the transparent pixel to the
 * first available position in the paletteInverter, which is palette_size. If you use
 * 256 colors no transparency will be used for the image we generate.
 * 
 * <p>
 * <strong>Be aware</strong> that IrfanView does not always report correctly
 * the size of the palette it exports. Be ready to manually correct the number
 * of colors reported.
 * 
 * <p>
 * Here is an explanation of what a JASC pal file should look like:
 * 
 * <a href="http://www.cryer.co.uk/filetypes/p/pal.htm">JASC PAL file</a>
 * 
 * and here is a list of other possible formats we could parse (in the future if
 * we have time or someone pays for it :-) )
 * 
 * <a href="http://www.pl32.com/forum/viewtopic.php?t=873">alternative PAL file formats</a>
 * 
 * @author Simone Giannecchini
 * 
 */
class PALFileLoader {
	protected static final Logger LOGGER = org.geotools.util.logging.Logging.getLogger("it.geosolutions.inversecolormap.PALFileLoader");

	/** Size of the color map we'll use. */
	protected int mapsize;

	/** Final index color model. */
	protected IndexColorModel indexColorModel;

	/**
	 * {@link PALFileLoader} constructor that accept a file path as a string.
	 * 
	 * <p>
	 * Note that the transparentIndex pixel should not exceed the last
	 * zero-based index available for the colormap we area going to create. If
	 * this happens we might get very bad behaviour. Note also that if we set
	 * this parameter to -1 we'll get an opaque {@link IndexColorModel}.
	 * 
	 * @param filePath
	 *            to the aplette file.
	 * @param transparentIndex
	 *            transparent pixel index (zero-based).
	 */
	public PALFileLoader(final String filePath) {
		this(new File(filePath));
	}

	/**
	 * {@link PALFileLoader} constructor that accept a file.
	 * 
	 * @see #PALFileLoader(String, int)
	 */
	public PALFileLoader(File file) {
		if (!file.exists() | !file.canRead())
			throw new IllegalArgumentException(
					"The provided file cannot be read.");
		BufferedReader reader = null;
		try {
			reader = new BufferedReader(new FileReader(file));
			// header
			boolean loadNext = false;
			String temp = reader.readLine().trim();
			if (temp.equalsIgnoreCase("JASC-PAL")) {
				if (LOGGER.isLoggable(Level.FINE))
					LOGGER.fine("Found header in palette file");
				loadNext = true;
			}

			// version
			if (loadNext) {
				temp = reader.readLine().trim();
				if (temp.equalsIgnoreCase("0100")) {
					if (LOGGER.isLoggable(Level.FINE))
						LOGGER.fine("Found version in palette file");
					loadNext = true;
				}
			}

			// num colors
			if (loadNext)
				temp = reader.readLine().trim();

			this.mapsize = Integer.parseInt(temp);
			if (mapsize > 256 || mapsize <= 0)
				throw new IllegalArgumentException(
						"The provided number of colors is invalid");

			// load various colors
			final byte colorMap[][] = new byte[3][mapsize<256?mapsize + 1:mapsize];
			for (int i = 0; i < mapsize; i++) {
				// get the line
				temp = reader.readLine().trim();

				if (temp.startsWith("#"))
					temp = "0x" + temp.substring(1);

				if (temp.startsWith("0x") || temp.startsWith("0X")) {
					final Color color = Color.decode(temp);
					colorMap[0][i] = (byte) color.getRed();
					colorMap[1][i] = (byte) color.getGreen();
					colorMap[2][i] = (byte) color.getBlue();
				} else {
					// tokenize it
					final StringTokenizer tokenizer = new StringTokenizer(temp,
							" ", false);
					int numComponents = 0;
					while (tokenizer.hasMoreTokens()) {
						if (numComponents >= 3)
							throw new IllegalArgumentException(
									"The number of components in one the color is greater than 3!");
						colorMap[numComponents++][i] = (byte) Integer
								.parseInt(tokenizer.nextToken());

					}
					if (numComponents != 3)
						throw new IllegalArgumentException(
								"The number of components in one the color is invalid!");
				}
			}

			// //
			//
			// create the index color model reserving space for the transparent
			// pixel is room exists.
			//
			////
			if (mapsize < 256)
				this.indexColorModel = new IndexColorModel(8, mapsize + 1,
						colorMap[0], colorMap[1], colorMap[2], mapsize);
			else
				this.indexColorModel = new IndexColorModel(8, mapsize,
						colorMap[0], colorMap[1], colorMap[2]);
		} catch (FileNotFoundException e) {
			LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
		} catch (IOException e) {
			LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);

		} catch (Exception e) {
			LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
		} finally {
			if (reader != null)
				try {
					reader.close();
				} catch (IOException e) {
					LOGGER.log(Level.INFO, e.getLocalizedMessage(), e);
				}
		}

	}

	public IndexColorModel getIndexColorModel() {
		return indexColorModel;
	}

	public int getMapsize() {
		return mapsize;
	}
}
